Chore/better lint rules v3#122
Conversation
|
Warning Review limit reached
Next review available in: 27 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (3)
📝 WalkthroughWalkthroughEste PR realiza un refactor extenso y transversal de estilo y saneamiento sin cambios funcionales sustanciales en la mayoría de archivos: reemplaza comprobaciones truthy por Object.hasOwn/comparaciones explícitas, migra window/global a globalThis, usa split limitado y replaceAll, reordena miembros de value objects, refactoriza casos de uso de carrito/personalizaciones y reescribe los contenedores de inyección de dependencias. ChangesRefactor transversal de saneamiento y consistencia
Estimated code review effort: 3 (Moderate) | ~25 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 3 | ❌ 1❌ Failed checks (1 inconclusive)
✅ Passed checks (3 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 5
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
modules/cart/presentation/components/cart-view.tsx (1)
95-100: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winInconsistencia entre la lógica del query param y la del renderizado para
sizevacío.En
buildCustomizationHref(líneas 99-100) se excluye explícitamente el tamaño cuando es una cadena vacía (customization.size.length > 0), pero en el renderizado del item (línea 320) solo se comprueba!= null, por lo que unsize === ''sí se mostrará en pantalla como{labels.customizationSize}:con valor vacío, mientras que no se incluirá en la URL de edición. Sería más consistente aplicar la misma comprobación de longitud en ambos sitios.🐛 Fix propuesto
- {item.customization.size != null && ( + {item.customization.size != null && + item.customization.size.length > 0 && ( <span className={styles.customizationLine}> {labels.customizationSize}: {item.customization.size} </span> )}Also applies to: 320-324
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@modules/cart/presentation/components/cart-view.tsx` around lines 95 - 100, The logic for customization size is inconsistent between buildCustomizationHref and the cart item render path in cart-view.tsx: empty size strings are filtered out for the edit URL but still rendered as an empty value. Update the render check for the size display to match the URL-building condition by using the same non-empty length validation as buildCustomizationHref, so size is only shown when it has content.modules/email/infrastructure/prisma-email-queue-repository.ts (1)
102-108: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winTypo en el comentario JSDoc: "atchSize" debería ser "batchSize".
✏️ Fix propuesto
- * Atomically claim up to atchSize entries that are due for processing. + * Atomically claim up to batchSize entries that are due for processing.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@modules/email/infrastructure/prisma-email-queue-repository.ts` around lines 102 - 108, The JSDoc comment on prisma-email-queue-repository.ts contains a typo in the parameter name, where “atchSize” should be “batchSize”. Update the documentation in the claim method area so the comment matches the intended identifier consistently and remains accurate for readers.
🧹 Nitpick comments (6)
modules/cart/presentation/components/add-to-cart-button.tsx (1)
86-91: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winLógica de "¿tiene contenido de personalización?" duplicada en 3 lugares.
La misma fórmula (
hasText(text) || hasText(color) || hasText(size) || hasText(imageUrl) || Boolean(designPosition)) se repite enisAuthCustomizationMatching, en el cálculo deisCustomizationHasContenta nivel de componente, y dentro deperformAuthenticatedAdd. Extraer un helper único reduciría el riesgo de que las tres copias diverjan si cambia el criterio.♻️ Refactor propuesto: helper compartido
const hasText = (value?: string | null) => value != null && value.length > 0; + +function hasCustomizationContent( + draft: { + text?: string | null; + color?: string | null; + size?: string | null; + imageUrl?: string | null; + designPosition?: unknown; + } | null, +): boolean { + if (!draft) return false; + return ( + hasText(draft.text) || + hasText(draft.color) || + hasText(draft.size) || + hasText(draft.imageUrl) || + Boolean(draft.designPosition) + ); +}Y reutilizarlo en los tres puntos (
isAuthCustomizationMatching,isCustomizationHasContent,performAuthenticatedAdd).Also applies to: 152-157, 354-360
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@modules/cart/presentation/components/add-to-cart-button.tsx` around lines 86 - 91, The “has customization content” check is duplicated in add-to-cart-button.tsx across isAuthCustomizationMatching, the isCustomizationHasContent component calculation, and performAuthenticatedAdd. Extract that shared logic into a single helper in this component/module (using the existing hasText, norm.text, norm.color, norm.size, norm.imageUrl, and norm.designPosition fields) and reuse it in all three places so the criterion stays consistent.shared/presentation/lib/to-absolute-url.ts (1)
6-10: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueInconsistencia menor entre
windowyglobalThis.Se comprueba
typeof window === 'undefined'pero luego se usaglobalThis.location.origin. Funcionalmente equivalente en el navegador, pero sería más consistente usarglobalThisen ambos puntos (owindowen ambos).🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@shared/presentation/lib/to-absolute-url.ts` around lines 6 - 10, The `toAbsoluteUrl` helper mixes `window` and `globalThis` for the environment check and URL resolution, which is inconsistent. Update the logic in `toAbsoluteUrl` to use the same global reference throughout the function, either by checking `globalThis` instead of `window` or by using `window` for both the guard and `location.origin` access.app/[locale]/products/[id]/customization-form.tsx (1)
60-65: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueInconsistencia de estilo entre campos
textysize.El campo
sizeahora usaerrors.size == nullmientras quetextsigue usando comprobación truthy (errors.text ? ..., líneas 62, 81 y 86, sin cambios). No es un bug pero sería más consistente aplicar el mismo criterio a ambos campos.Also applies to: 137-137, 147-147
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/`[locale]/products/[id]/customization-form.tsx around lines 60 - 65, The error ID checks in customization-form.tsx are inconsistent between the text and size fields; update the remaining text-related logic in CustomizationForm to use the same null/undefined check pattern as size. Specifically, align the conditional used for textErrorId and any related text field error handling with the errors.size == null approach so the form uses a consistent convention across both fields.app/[locale]/auth/verify-email/page.tsx (1)
25-44: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winConsiderar ignorar
AbortErroren elcatch.Si
tokencambiara y el efecto se reejecutara, elcatchde la petición abortada anterior también llamaría asetStatus('invalid'), pudiendo pisar el estado correcto de la nueva petición en curso. Es un caso de baja probabilidad (token normalmente fijo), pero fácil de blindar comprobandocontroller.signal.abortedantes de actualizar el estado.🛡️ Fix sugerido
} catch { - setStatus('invalid'); + } catch { + if (!controller.signal.aborted) { + setStatus('invalid'); + } }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/`[locale]/auth/verify-email/page.tsx around lines 25 - 44, The verify-email request handler in the async effect can wrongly set status to invalid after an aborted fetch when the token changes. Update the try/catch flow in the verify-email page component so the catch block checks the AbortController state (for example via controller.signal.aborted) and skips setStatus on aborted requests, only mapping real failures to invalid. Keep the behavior aligned with the existing success/expired/invalid logic in the effect that calls fetch and setStatus.composition-root/container.ts (1)
105-105: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚖️ Poor tradeoffTipado débil:
Record<string, unknown>para el estado central del contenedor.Cada getter/setter requiere castear (
as X) el valor almacenado enstate, perdiendo la verificación en tiempo de compilación que ofrecían las variables individuales tipadas previas. Un typo en la clave (state.emailSedneren vez destate.emailSender, por ejemplo) no sería detectado hasta ejecución.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@composition-root/container.ts` at line 105, El estado central del contenedor está tipado demasiado débilmente con Record<string, unknown>, lo que obliga a castings en cada acceso y elimina la verificación de claves y tipos. Refactoriza el objeto state en composition-root/container.ts para usar un tipo explícito con propiedades nombradas para cada servicio/configuración, y actualiza los getters/setters relacionados para leer y escribir con ese tipo en lugar de depender de as X, usando los símbolos state y los accesores del contenedor para ubicar los cambios.shared/kernel/container.ts (1)
24-24: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚖️ Poor tradeoffTipado débil:
state: Record<string, unknown>reemplaza variables tipadas.El uso de
Record<string, unknown>con castsas EmailSenderen cada getter/setter renuncia a la seguridad de tipos que ofrecían las variableslettipadas previas. Un error de clave (typo) no sería detectado en tiempo de compilación.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@shared/kernel/container.ts` at line 24, The `state: Record<string, unknown>` map in `container.ts` weakens type safety and forces `as EmailSender` casts in the related getters/setters. Replace the untyped bag with explicit typed storage for each dependency or a strongly typed container interface, and update the accessor functions to read/write through those typed symbols so typos in keys are caught at compile time.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@modules/cart/presentation/components/cart-popup.tsx`:
- Around line 115-175: The cart popup fetch flow in cart-popup.tsx has a race
condition because the same AbortController is reused across the initial load and
CART_UPDATED_EVENT refreshes. Update the doFetch logic so each request gets its
own AbortController, and abort any previous in-flight request before starting a
new one; keep the latest response as the only one allowed to call setAuthItems
and setLoading. Use the existing doFetch, ctrl, and handleCartUpdated symbols to
locate and refactor the effect.
In `@modules/customizations/application/create-customer-customization.ts`:
- Around line 63-94: The validation logic in assertModeRequirement is using the
wrong error precedence for mode 'text_photo': the current checks in
CreateCustomerCustomizationApplication can never produce the right message when
both fields are missing, and the final ValidationError in the text_photo branch
is misleading when only text is missing. Update the condition order and/or
branching so the text_photo case is handled explicitly with the correct message
for each missing-field combination, using the assertModeRequirement method and
its hasText/hasImageForCapability checks to distinguish “missing photo”,
“missing text”, and “missing both”.
In `@modules/orders/application/assign-to-production-use-case.ts`:
- Around line 31-35: The JSDoc `@example` blocks in AssignToProductionUseCase have
corrupted Markdown fences, where the opening or closing triple backticks are
split by a tab and break syntax highlighting. Fix the examples in
assign-to-production-use-case.ts by normalizing each fenced code block in the
affected JSDoc comments so the backticks are valid Markdown, and apply the same
correction consistently across all three examples referenced in the class
documentation.
In `@modules/orders/application/mark-as-paid-use-case.ts`:
- Around line 56-63: The PAYMENT_COMPLETED listener in
MarkAsPaidUseCase.subscribe is swallowing exceptions by only logging them, which
makes EventBusPort.emit think processing succeeded. Update the async handler to
rethrow the error after catching it, or route it through the bus’s retry/DLQ
path if available, so failures from useCase.execute(data as MarkAsPaidDTO) are
not hidden and the event contract remains intact.
In `@prisma/seed.ts`:
- Line 608: The error log in the seed failure handler uses a corrupted character
in the message, so update the console.error call in seed.ts to restore the
intended emoji/text instead of the mojibake string. Check the surrounding seed
logging style used in the same script and replace the broken prefix in the catch
block with the correct failure marker so it matches the other emoji-based logs.
---
Outside diff comments:
In `@modules/cart/presentation/components/cart-view.tsx`:
- Around line 95-100: The logic for customization size is inconsistent between
buildCustomizationHref and the cart item render path in cart-view.tsx: empty
size strings are filtered out for the edit URL but still rendered as an empty
value. Update the render check for the size display to match the URL-building
condition by using the same non-empty length validation as
buildCustomizationHref, so size is only shown when it has content.
In `@modules/email/infrastructure/prisma-email-queue-repository.ts`:
- Around line 102-108: The JSDoc comment on prisma-email-queue-repository.ts
contains a typo in the parameter name, where “atchSize” should be “batchSize”.
Update the documentation in the claim method area so the comment matches the
intended identifier consistently and remains accurate for readers.
---
Nitpick comments:
In `@app/`[locale]/auth/verify-email/page.tsx:
- Around line 25-44: The verify-email request handler in the async effect can
wrongly set status to invalid after an aborted fetch when the token changes.
Update the try/catch flow in the verify-email page component so the catch block
checks the AbortController state (for example via controller.signal.aborted) and
skips setStatus on aborted requests, only mapping real failures to invalid. Keep
the behavior aligned with the existing success/expired/invalid logic in the
effect that calls fetch and setStatus.
In `@app/`[locale]/products/[id]/customization-form.tsx:
- Around line 60-65: The error ID checks in customization-form.tsx are
inconsistent between the text and size fields; update the remaining text-related
logic in CustomizationForm to use the same null/undefined check pattern as size.
Specifically, align the conditional used for textErrorId and any related text
field error handling with the errors.size == null approach so the form uses a
consistent convention across both fields.
In `@composition-root/container.ts`:
- Line 105: El estado central del contenedor está tipado demasiado débilmente
con Record<string, unknown>, lo que obliga a castings en cada acceso y elimina
la verificación de claves y tipos. Refactoriza el objeto state en
composition-root/container.ts para usar un tipo explícito con propiedades
nombradas para cada servicio/configuración, y actualiza los getters/setters
relacionados para leer y escribir con ese tipo en lugar de depender de as X,
usando los símbolos state y los accesores del contenedor para ubicar los
cambios.
In `@modules/cart/presentation/components/add-to-cart-button.tsx`:
- Around line 86-91: The “has customization content” check is duplicated in
add-to-cart-button.tsx across isAuthCustomizationMatching, the
isCustomizationHasContent component calculation, and performAuthenticatedAdd.
Extract that shared logic into a single helper in this component/module (using
the existing hasText, norm.text, norm.color, norm.size, norm.imageUrl, and
norm.designPosition fields) and reuse it in all three places so the criterion
stays consistent.
In `@shared/kernel/container.ts`:
- Line 24: The `state: Record<string, unknown>` map in `container.ts` weakens
type safety and forces `as EmailSender` casts in the related getters/setters.
Replace the untyped bag with explicit typed storage for each dependency or a
strongly typed container interface, and update the accessor functions to
read/write through those typed symbols so typos in keys are caught at compile
time.
In `@shared/presentation/lib/to-absolute-url.ts`:
- Around line 6-10: The `toAbsoluteUrl` helper mixes `window` and `globalThis`
for the environment check and URL resolution, which is inconsistent. Update the
logic in `toAbsoluteUrl` to use the same global reference throughout the
function, either by checking `globalThis` instead of `window` or by using
`window` for both the guard and `location.origin` access.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 33ab3ddf-b8d8-4c46-bae2-07e60b1768a6
📒 Files selected for processing (124)
app/[locale]/admin/sellers/create/page.tsxapp/[locale]/admin/sellers/page.tsxapp/[locale]/auth/change-password/page.tsxapp/[locale]/auth/reset-password/page.tsxapp/[locale]/auth/signup/page.tsxapp/[locale]/auth/verify-email/page.tsxapp/[locale]/cart/design-preview.tsxapp/[locale]/checkout/page.tsxapp/[locale]/layout.tsxapp/[locale]/orders/[orderId]/page.tsxapp/[locale]/orders/page.tsxapp/[locale]/page.tsxapp/[locale]/products/[id]/customization-experience.tsxapp/[locale]/products/[id]/customization-form.tsxapp/[locale]/products/[id]/customization-preview.tsxapp/[locale]/products/[id]/page.tsxapp/[locale]/products/[id]/photo-upload-field.tsxapp/[locale]/profile/page.tsxapp/[locale]/seller/orders/page.tsxapp/[locale]/seller/products/page.tsxapp/[locale]/seller/products/product-form.tsxapp/api/auth/[...nextauth]/route.tsapp/api/auth/forgot-password/route.tsapp/api/auth/reset-password/route.tsapp/api/customizations/route.tsapp/api/sellers/route.tsapp/api/uploads/guest/presigned-url/route.tsapp/api/uploads/local/[...storageKey]/route.tscomponents/products/infinite-product-list.tsxcomponents/products/search-input-with-suggestions.tsxcomposition-root/container.tseslint.config.mjsmodules/auth/domain/rate-limiter.tsmodules/auth/domain/session.tsmodules/auth/domain/user-lookup.tsmodules/auth/domain/value-objects/verification-token.tsmodules/auth/infrastructure/prisma-rate-limiter.tsmodules/cart/application/add-item-to-cart.tsmodules/cart/application/checkout-cart.tsmodules/cart/application/migrate-guest-cart.tsmodules/cart/application/update-cart-item.tsmodules/cart/domain/cart-repository.tsmodules/cart/domain/value-objects/cart-id.tsmodules/cart/domain/value-objects/cart-item-id.tsmodules/cart/domain/value-objects/quantity.tsmodules/cart/infrastructure/customization-lookup-adapter.tsmodules/cart/infrastructure/prisma-cart-repository.tsmodules/cart/presentation/components/add-to-cart-button.tsxmodules/cart/presentation/components/cart-icon.tsxmodules/cart/presentation/components/cart-merge-detector.tsxmodules/cart/presentation/components/cart-popup.tsxmodules/cart/presentation/components/cart-view.tsxmodules/cart/presentation/components/checkout-confirm-button.tsxmodules/cart/presentation/components/merge-dialog.tsxmodules/cart/presentation/guest-cart-context.tsxmodules/customizations/application/create-customer-customization.tsmodules/customizations/application/update-customization.tsmodules/customizations/domain/value-objects/customization-options.tsmodules/customizations/infrastructure/prisma-customization-repository.tsmodules/email/infrastructure/prisma-email-queue-repository.tsmodules/orders/application/assign-to-production-use-case.tsmodules/orders/application/handle-cart-checked-out.tsmodules/orders/application/mark-as-paid-use-case.tsmodules/orders/domain/order-repository.tsmodules/presentation/components/design-preview.tsxmodules/products/application/update-product-use-case.tsmodules/products/domain/value-objects/category-id.tsmodules/products/domain/value-objects/product-customization-config.tsmodules/products/domain/value-objects/product-description.tsmodules/products/domain/value-objects/product-name.tsmodules/products/domain/value-objects/product-price.tsmodules/products/infrastructure/prisma-product-repository.tsmodules/products/presentation/components/product-actions.tsxmodules/roles/infrastructure/prisma-role-repository.tsmodules/search-history/application/handle-product-search-executed.tsmodules/search-history/domain/search-history-repository.tsmodules/sellers/infrastructure/prisma-seller-repository.tsmodules/sellers/presentation/components/seller-detail-form.tsxmodules/uploads/application/cleanup-uploads-use-case.tsmodules/uploads/application/create-upload-use-case.tsmodules/uploads/application/delete-upload-use-case.tsmodules/uploads/domain/upload-repository.tsmodules/uploads/domain/value-objects/upload-status.tsmodules/uploads/domain/value-objects/upload-type.tsmodules/uploads/infrastructure/r2-storage-adapter.tsmodules/users/domain/user-repository.tsprisma/seed.tsproxy.tsscripts/optimize-sprites.mjsshared/contracts/email/email-queue-port.tsshared/i18n/get-dictionary.tsshared/infrastructure/auth-options.tsshared/infrastructure/prisma.tsshared/kernel/container.tsshared/kernel/domain/identifiers/role-id.tsshared/kernel/domain/value-objects/address.tsshared/kernel/domain/value-objects/email.tsshared/kernel/domain/value-objects/localized-date.tsshared/kernel/domain/value-objects/money.tsshared/kernel/domain/value-objects/order-id.tsshared/kernel/domain/value-objects/password-hash.tsshared/kernel/domain/value-objects/payment-id.tsshared/kernel/domain/value-objects/product-id.tsshared/kernel/domain/value-objects/seller-id.tsshared/kernel/domain/value-objects/ticket-id.tsshared/kernel/domain/value-objects/user-id.tsshared/kernel/escape-html.tsshared/layout/header-nav.tsxshared/layout/language-selector.tsxshared/layout/login-modal.tsxshared/lib/normalize-text.tsshared/presentation/components/file-upload-dropzone.tsxshared/presentation/lib/to-absolute-url.tsshared/presentation/status-labels.tsshared/ui/auth-card.tsxshared/ui/eye-toggle-wrapper.tsxshared/ui/file-upload-dropzone.tsxshared/ui/modal.tsxshared/ui/password-strength-indicator.tsxshared/ui/price-field.tsxshared/ui/quantity-controls.tsxshared/ui/tag-list.tsxshared/ui/wave-transition.tsxtests/doubles/memory-rate-limiter.ts
| useEffect(() => { | ||
| if (!isOpen || !isAuthenticated) return; | ||
| abortRef.current?.abort(); | ||
| const ctrl = new AbortController(); | ||
| abortRef.current = ctrl; | ||
| setLoading(true); | ||
| fetch('/api/cart', { signal: ctrl.signal }) | ||
| .then((r) => (r.ok ? r.json() : { items: [] })) | ||
| .then((data) => { | ||
| if (!ctrl.signal.aborted) { | ||
| setAuthItems( | ||
| (data.items ?? []).map((i: Record<string, unknown>) => { | ||
| const customizations = | ||
| (i.customizations as Array<Record<string, unknown>>) ?? []; | ||
| const firstC = customizations[0] ?? null; | ||
| return { | ||
| id: i.id as string, | ||
| productId: i.productId as string, | ||
| productName: i.productName as string, | ||
| productImageUrl: | ||
| (i.colorImageUrl as string | null) ?? | ||
| (i.productImageUrl as string | null) ?? | ||
| null, | ||
| sellerId: i.sellerId as string, | ||
| sellerName: i.sellerName as string, | ||
| quantity: i.quantity as number, | ||
| unitPrice: i.unitPrice as number, | ||
| lineTotal: +( | ||
| (i.unitPrice as number) * (i.quantity as number) | ||
| ).toFixed(2), | ||
| customization: firstC | ||
| ? { | ||
| text: (firstC.text as string | null) ?? null, | ||
| color: (firstC.color as string | null) ?? null, | ||
| size: (firstC.size as string | null) ?? null, | ||
| imageUrl: (firstC.imageUrl as string | null) ?? null, | ||
| designPosition: | ||
| (firstC.designPosition as Record<string, unknown>) ?? | ||
| null, | ||
| } | ||
| : null, | ||
| } as CartItemDTO; | ||
| }), | ||
| ); | ||
| const doFetch = async () => { | ||
| setLoading(true); | ||
| try { | ||
| const res = await fetch('/api/cart', { signal: ctrl.signal }); | ||
| if (ctrl.signal.aborted) return; | ||
| if (!res.ok) { | ||
| setLoading(false); | ||
| return; | ||
| } | ||
| }) | ||
| .catch(() => { | ||
| const data = await res.json(); | ||
| if (ctrl.signal.aborted) return; | ||
| setAuthItems( | ||
| (data.items ?? []).map((i: Record<string, unknown>) => { | ||
| const customizations = | ||
| (i.customizations as Array<Record<string, unknown>>) ?? []; | ||
| const firstC = customizations[0] ?? null; | ||
| return { | ||
| id: i.id as string, | ||
| productId: i.productId as string, | ||
| productName: i.productName as string, | ||
| productImageUrl: | ||
| (i.colorImageUrl as string | null) ?? | ||
| (i.productImageUrl as string | null) ?? | ||
| null, | ||
| sellerId: i.sellerId as string, | ||
| sellerName: i.sellerName as string, | ||
| quantity: i.quantity as number, | ||
| unitPrice: i.unitPrice as number, | ||
| lineTotal: +( | ||
| (i.unitPrice as number) * (i.quantity as number) | ||
| ).toFixed(2), | ||
| customization: firstC | ||
| ? { | ||
| text: (firstC.text as string | null) ?? null, | ||
| color: (firstC.color as string | null) ?? null, | ||
| size: (firstC.size as string | null) ?? null, | ||
| imageUrl: (firstC.imageUrl as string | null) ?? null, | ||
| designPosition: | ||
| (firstC.designPosition as Record<string, unknown>) ?? | ||
| null, | ||
| } | ||
| : null, | ||
| } as CartItemDTO; | ||
| }), | ||
| ); | ||
| setLoading(false); | ||
| } catch { | ||
| if (!ctrl.signal.aborted) setLoading(false); | ||
| }); | ||
| }, [isOpen, isAuthenticated]); | ||
|
|
||
| useEffect(() => { | ||
| if (!isOpen || !isAuthenticated) return; | ||
| refreshAuthCart(); | ||
| const handleCartUpdated = () => refreshAuthCart(); | ||
| window.addEventListener(CART_UPDATED_EVENT, handleCartUpdated); | ||
| } | ||
| }; | ||
| doFetch(); | ||
| const handleCartUpdated = () => doFetch(); | ||
| globalThis.addEventListener(CART_UPDATED_EVENT, handleCartUpdated); | ||
| return () => { | ||
| abortRef.current?.abort(); | ||
| window.removeEventListener(CART_UPDATED_EVENT, handleCartUpdated); | ||
| ctrl.abort(); | ||
| globalThis.removeEventListener(CART_UPDATED_EVENT, handleCartUpdated); | ||
| }; | ||
| }, [isOpen, isAuthenticated, refreshAuthCart]); | ||
| }, [isOpen, isAuthenticated]); |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win
Condición de carrera: peticiones doFetch concurrentes no se cancelan entre sí.
El AbortController (ctrl) se crea una sola vez por ejecución del efecto y se reutiliza tanto en la carga inicial como en cada disparo de CART_UPDATED_EVENT. Si el evento se dispara mientras una petición anterior sigue en vuelo (p.ej. clics rápidos en los controles de cantidad, cada uno llamando a dispatchCartUpdated() al completarse), la nueva llamada a doFetch() no cancela la anterior. Las respuestas pueden resolver fuera de orden y la que complete al final (no necesariamente la más reciente) sobrescribe authItems, mostrando temporalmente datos desactualizados del carrito.
🔧 Fix sugerido: crear un nuevo `AbortController` por cada `doFetch`, abortando el anterior
useEffect(() => {
if (!isOpen || !isAuthenticated) return;
- const ctrl = new AbortController();
+ let ctrl = new AbortController();
const doFetch = async () => {
+ ctrl.abort();
+ ctrl = new AbortController();
+ const localCtrl = ctrl;
setLoading(true);
try {
- const res = await fetch('/api/cart', { signal: ctrl.signal });
- if (ctrl.signal.aborted) return;
+ const res = await fetch('/api/cart', { signal: localCtrl.signal });
+ if (localCtrl.signal.aborted) return;
if (!res.ok) {
setLoading(false);
return;
}
const data = await res.json();
- if (ctrl.signal.aborted) return;
+ if (localCtrl.signal.aborted) return;
...
- setLoading(false);
+ setLoading(false);
} catch {
- if (!ctrl.signal.aborted) setLoading(false);
+ if (!localCtrl.signal.aborted) setLoading(false);
}
};📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| useEffect(() => { | |
| if (!isOpen || !isAuthenticated) return; | |
| abortRef.current?.abort(); | |
| const ctrl = new AbortController(); | |
| abortRef.current = ctrl; | |
| setLoading(true); | |
| fetch('/api/cart', { signal: ctrl.signal }) | |
| .then((r) => (r.ok ? r.json() : { items: [] })) | |
| .then((data) => { | |
| if (!ctrl.signal.aborted) { | |
| setAuthItems( | |
| (data.items ?? []).map((i: Record<string, unknown>) => { | |
| const customizations = | |
| (i.customizations as Array<Record<string, unknown>>) ?? []; | |
| const firstC = customizations[0] ?? null; | |
| return { | |
| id: i.id as string, | |
| productId: i.productId as string, | |
| productName: i.productName as string, | |
| productImageUrl: | |
| (i.colorImageUrl as string | null) ?? | |
| (i.productImageUrl as string | null) ?? | |
| null, | |
| sellerId: i.sellerId as string, | |
| sellerName: i.sellerName as string, | |
| quantity: i.quantity as number, | |
| unitPrice: i.unitPrice as number, | |
| lineTotal: +( | |
| (i.unitPrice as number) * (i.quantity as number) | |
| ).toFixed(2), | |
| customization: firstC | |
| ? { | |
| text: (firstC.text as string | null) ?? null, | |
| color: (firstC.color as string | null) ?? null, | |
| size: (firstC.size as string | null) ?? null, | |
| imageUrl: (firstC.imageUrl as string | null) ?? null, | |
| designPosition: | |
| (firstC.designPosition as Record<string, unknown>) ?? | |
| null, | |
| } | |
| : null, | |
| } as CartItemDTO; | |
| }), | |
| ); | |
| const doFetch = async () => { | |
| setLoading(true); | |
| try { | |
| const res = await fetch('/api/cart', { signal: ctrl.signal }); | |
| if (ctrl.signal.aborted) return; | |
| if (!res.ok) { | |
| setLoading(false); | |
| return; | |
| } | |
| }) | |
| .catch(() => { | |
| const data = await res.json(); | |
| if (ctrl.signal.aborted) return; | |
| setAuthItems( | |
| (data.items ?? []).map((i: Record<string, unknown>) => { | |
| const customizations = | |
| (i.customizations as Array<Record<string, unknown>>) ?? []; | |
| const firstC = customizations[0] ?? null; | |
| return { | |
| id: i.id as string, | |
| productId: i.productId as string, | |
| productName: i.productName as string, | |
| productImageUrl: | |
| (i.colorImageUrl as string | null) ?? | |
| (i.productImageUrl as string | null) ?? | |
| null, | |
| sellerId: i.sellerId as string, | |
| sellerName: i.sellerName as string, | |
| quantity: i.quantity as number, | |
| unitPrice: i.unitPrice as number, | |
| lineTotal: +( | |
| (i.unitPrice as number) * (i.quantity as number) | |
| ).toFixed(2), | |
| customization: firstC | |
| ? { | |
| text: (firstC.text as string | null) ?? null, | |
| color: (firstC.color as string | null) ?? null, | |
| size: (firstC.size as string | null) ?? null, | |
| imageUrl: (firstC.imageUrl as string | null) ?? null, | |
| designPosition: | |
| (firstC.designPosition as Record<string, unknown>) ?? | |
| null, | |
| } | |
| : null, | |
| } as CartItemDTO; | |
| }), | |
| ); | |
| setLoading(false); | |
| } catch { | |
| if (!ctrl.signal.aborted) setLoading(false); | |
| }); | |
| }, [isOpen, isAuthenticated]); | |
| useEffect(() => { | |
| if (!isOpen || !isAuthenticated) return; | |
| refreshAuthCart(); | |
| const handleCartUpdated = () => refreshAuthCart(); | |
| window.addEventListener(CART_UPDATED_EVENT, handleCartUpdated); | |
| } | |
| }; | |
| doFetch(); | |
| const handleCartUpdated = () => doFetch(); | |
| globalThis.addEventListener(CART_UPDATED_EVENT, handleCartUpdated); | |
| return () => { | |
| abortRef.current?.abort(); | |
| window.removeEventListener(CART_UPDATED_EVENT, handleCartUpdated); | |
| ctrl.abort(); | |
| globalThis.removeEventListener(CART_UPDATED_EVENT, handleCartUpdated); | |
| }; | |
| }, [isOpen, isAuthenticated, refreshAuthCart]); | |
| }, [isOpen, isAuthenticated]); | |
| useEffect(() => { | |
| if (!isOpen || !isAuthenticated) return; | |
| let ctrl = new AbortController(); | |
| const doFetch = async () => { | |
| ctrl.abort(); | |
| ctrl = new AbortController(); | |
| const localCtrl = ctrl; | |
| setLoading(true); | |
| try { | |
| const res = await fetch('/api/cart', { signal: localCtrl.signal }); | |
| if (localCtrl.signal.aborted) return; | |
| if (!res.ok) { | |
| setLoading(false); | |
| return; | |
| } | |
| const data = await res.json(); | |
| if (localCtrl.signal.aborted) return; | |
| setAuthItems( | |
| (data.items ?? []).map((i: Record<string, unknown>) => { | |
| const customizations = | |
| (i.customizations as Array<Record<string, unknown>>) ?? []; | |
| const firstC = customizations[0] ?? null; | |
| return { | |
| id: i.id as string, | |
| productId: i.productId as string, | |
| productName: i.productName as string, | |
| productImageUrl: | |
| (i.colorImageUrl as string | null) ?? | |
| (i.productImageUrl as string | null) ?? | |
| null, | |
| sellerId: i.sellerId as string, | |
| sellerName: i.sellerName as string, | |
| quantity: i.quantity as number, | |
| unitPrice: i.unitPrice as number, | |
| lineTotal: +( | |
| (i.unitPrice as number) * (i.quantity as number) | |
| ).toFixed(2), | |
| customization: firstC | |
| ? { | |
| text: (firstC.text as string | null) ?? null, | |
| color: (firstC.color as string | null) ?? null, | |
| size: (firstC.size as string | null) ?? null, | |
| imageUrl: (firstC.imageUrl as string | null) ?? null, | |
| designPosition: | |
| (firstC.designPosition as Record<string, unknown>) ?? | |
| null, | |
| } | |
| : null, | |
| } as CartItemDTO; | |
| }), | |
| ); | |
| setLoading(false); | |
| } catch { | |
| if (!localCtrl.signal.aborted) setLoading(false); | |
| } | |
| }; | |
| doFetch(); | |
| const handleCartUpdated = () => doFetch(); | |
| globalThis.addEventListener(CART_UPDATED_EVENT, handleCartUpdated); | |
| return () => { | |
| ctrl.abort(); | |
| globalThis.removeEventListener(CART_UPDATED_EVENT, handleCartUpdated); | |
| }; | |
| }, [isOpen, isAuthenticated]); |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@modules/cart/presentation/components/cart-popup.tsx` around lines 115 - 175,
The cart popup fetch flow in cart-popup.tsx has a race condition because the
same AbortController is reused across the initial load and CART_UPDATED_EVENT
refreshes. Update the doFetch logic so each request gets its own
AbortController, and abort any previous in-flight request before starting a new
one; keep the latest response as the only one allowed to call setAuthItems and
setLoading. Use the existing doFetch, ctrl, and handleCartUpdated symbols to
locate and refactor the effect.
| private assertModeRequirement( | ||
| dto: CreateCustomerCustomizationDTO, | ||
| config: ProductCustomizationConfig, | ||
| ): void { | ||
| const hasText = dto.text !== undefined && dto.text !== null; | ||
| const hasImage = dto.imageUrl !== undefined && dto.imageUrl !== null; | ||
| const hasDesignPosition = | ||
| dto.designPosition !== undefined && dto.designPosition !== null; | ||
| const hasImageForCapability = hasImage || hasDesignPosition; | ||
| const mode = config.mode; | ||
|
|
||
| switch (config.mode) { | ||
| case 'description': | ||
| case 'text': | ||
| if (!hasText) { | ||
| throw new ValidationError( | ||
| 'Text customization is required for this product', | ||
| 'Customization is not allowed for this product', | ||
| ); | ||
| } | ||
| break; | ||
| case 'photo': | ||
| if (!hasImageForCapability) { | ||
| throw new ValidationError( | ||
| 'Photo customization is required for this product', | ||
| 'Customization is not allowed for this product', | ||
| ); | ||
| } | ||
| break; | ||
| case 'text_photo': | ||
| if (!hasText && !hasImageForCapability) { | ||
| throw new ValidationError( | ||
| 'Text or photo customization is required for this product', | ||
| 'Customization is not allowed for this product', | ||
| ); | ||
| } | ||
| break; | ||
| if ((mode === 'description' || mode === 'text') && !hasText) { | ||
| throw new ValidationError( | ||
| 'Text customization is required for this product', | ||
| 'Customization is not allowed for this product', | ||
| ); | ||
| } | ||
|
|
||
| if ((mode === 'photo' || mode === 'text_photo') && !hasImageForCapability) { | ||
| throw new ValidationError( | ||
| 'Photo customization is required for this product', | ||
| 'Customization is not allowed for this product', | ||
| ); | ||
| } | ||
|
|
||
| if (mode === 'text_photo' && (!hasText || !hasImageForCapability)) { | ||
| throw new ValidationError( | ||
| 'Both text and photo customization are required for this product', | ||
| 'Customization is not allowed for this product', | ||
| ); | ||
| } | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Mensajes de error incorrectos para el modo text_photo.
El tercer if (línea 88) es prácticamente inalcanzable en su forma correcta: si falta la foto, el segundo if (línea 81) ya lanza antes con el mensaje "Photo customization is required", por lo que el tercer if solo se ejecuta cuando la foto SÍ está presente y falta el texto — pero en ese caso lanza el mensaje "Both text and photo customization are required", que es engañoso (solo falta el texto). Igualmente, cuando faltan ambos, el mensaje dice solo "Photo customization is required" sin mencionar el texto.
🐛 Fix propuesto: precedencia correcta de mensajes para `text_photo`
- if ((mode === 'photo' || mode === 'text_photo') && !hasImageForCapability) {
- throw new ValidationError(
- 'Photo customization is required for this product',
- 'Customization is not allowed for this product',
- );
- }
-
- if (mode === 'text_photo' && (!hasText || !hasImageForCapability)) {
- throw new ValidationError(
- 'Both text and photo customization are required for this product',
- 'Customization is not allowed for this product',
- );
- }
+ if (mode === 'photo' && !hasImageForCapability) {
+ throw new ValidationError(
+ 'Photo customization is required for this product',
+ 'Customization is not allowed for this product',
+ );
+ }
+
+ if (mode === 'text_photo') {
+ if (!hasText && !hasImageForCapability) {
+ throw new ValidationError(
+ 'Both text and photo customization are required for this product',
+ 'Customization is not allowed for this product',
+ );
+ }
+ if (!hasText) {
+ throw new ValidationError(
+ 'Text customization is required for this product',
+ 'Customization is not allowed for this product',
+ );
+ }
+ if (!hasImageForCapability) {
+ throw new ValidationError(
+ 'Photo customization is required for this product',
+ 'Customization is not allowed for this product',
+ );
+ }
+ }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| private assertModeRequirement( | |
| dto: CreateCustomerCustomizationDTO, | |
| config: ProductCustomizationConfig, | |
| ): void { | |
| const hasText = dto.text !== undefined && dto.text !== null; | |
| const hasImage = dto.imageUrl !== undefined && dto.imageUrl !== null; | |
| const hasDesignPosition = | |
| dto.designPosition !== undefined && dto.designPosition !== null; | |
| const hasImageForCapability = hasImage || hasDesignPosition; | |
| const mode = config.mode; | |
| switch (config.mode) { | |
| case 'description': | |
| case 'text': | |
| if (!hasText) { | |
| throw new ValidationError( | |
| 'Text customization is required for this product', | |
| 'Customization is not allowed for this product', | |
| ); | |
| } | |
| break; | |
| case 'photo': | |
| if (!hasImageForCapability) { | |
| throw new ValidationError( | |
| 'Photo customization is required for this product', | |
| 'Customization is not allowed for this product', | |
| ); | |
| } | |
| break; | |
| case 'text_photo': | |
| if (!hasText && !hasImageForCapability) { | |
| throw new ValidationError( | |
| 'Text or photo customization is required for this product', | |
| 'Customization is not allowed for this product', | |
| ); | |
| } | |
| break; | |
| if ((mode === 'description' || mode === 'text') && !hasText) { | |
| throw new ValidationError( | |
| 'Text customization is required for this product', | |
| 'Customization is not allowed for this product', | |
| ); | |
| } | |
| if ((mode === 'photo' || mode === 'text_photo') && !hasImageForCapability) { | |
| throw new ValidationError( | |
| 'Photo customization is required for this product', | |
| 'Customization is not allowed for this product', | |
| ); | |
| } | |
| if (mode === 'text_photo' && (!hasText || !hasImageForCapability)) { | |
| throw new ValidationError( | |
| 'Both text and photo customization are required for this product', | |
| 'Customization is not allowed for this product', | |
| ); | |
| } | |
| } | |
| private assertModeRequirement( | |
| dto: CreateCustomerCustomizationDTO, | |
| config: ProductCustomizationConfig, | |
| ): void { | |
| const hasText = dto.text !== undefined && dto.text !== null; | |
| const hasImage = dto.imageUrl !== undefined && dto.imageUrl !== null; | |
| const hasDesignPosition = | |
| dto.designPosition !== undefined && dto.designPosition !== null; | |
| const hasImageForCapability = hasImage || hasDesignPosition; | |
| const mode = config.mode; | |
| if ((mode === 'description' || mode === 'text') && !hasText) { | |
| throw new ValidationError( | |
| 'Text customization is required for this product', | |
| 'Customization is not allowed for this product', | |
| ); | |
| } | |
| if (mode === 'photo' && !hasImageForCapability) { | |
| throw new ValidationError( | |
| 'Photo customization is required for this product', | |
| 'Customization is not allowed for this product', | |
| ); | |
| } | |
| if (mode === 'text_photo') { | |
| if (!hasText && !hasImageForCapability) { | |
| throw new ValidationError( | |
| 'Both text and photo customization are required for this product', | |
| 'Customization is not allowed for this product', | |
| ); | |
| } | |
| if (!hasText) { | |
| throw new ValidationError( | |
| 'Text customization is required for this product', | |
| 'Customization is not allowed for this product', | |
| ); | |
| } | |
| if (!hasImageForCapability) { | |
| throw new ValidationError( | |
| 'Photo customization is required for this product', | |
| 'Customization is not allowed for this product', | |
| ); | |
| } | |
| } | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@modules/customizations/application/create-customer-customization.ts` around
lines 63 - 94, The validation logic in assertModeRequirement is using the wrong
error precedence for mode 'text_photo': the current checks in
CreateCustomerCustomizationApplication can never produce the right message when
both fields are missing, and the final ValidationError in the text_photo branch
is misleading when only text is missing. Update the condition order and/or
branching so the text_photo case is handled explicitly with the correct message
for each missing-field combination, using the assertModeRequirement method and
its hasText/hasImageForCapability checks to distinguish “missing photo”,
“missing text”, and “missing both”.
| * ` ypescript | ||
| * const useCase = new AssignToProductionUseCase(orderRepo, outboxRepo, transactionalService); | ||
| * await useCase.execute({ orderId: 'order-1', customizationId: 'custom-1' }); | ||
| * ``` | ||
| * ` | ||
| */ |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Bloques de código Markdown corruptos en los comentarios JSDoc.
Las vallas de código triples (```typescript) aparecen como un backtick seguido de un tabulador (` ypescript), lo que rompe el resaltado de sintaxis en el ejemplo @example en herramientas que renderizan JSDoc (IDEs, generadores de documentación). Ocurre en tres lugares: líneas 31-34, 50-53 y 101-106.
📝 Fix propuesto
- * ` ypescript
+ * ```typescript
* const useCase = new AssignToProductionUseCase(orderRepo, outboxRepo, transactionalService);
* await useCase.execute({ orderId: 'order-1', customizationId: 'custom-1' });
- * `
+ * ```
*/Aplicar el mismo arreglo a los otros dos bloques de ejemplo (líneas 50-53 y 101-106).
Also applies to: 50-53, 101-106
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@modules/orders/application/assign-to-production-use-case.ts` around lines 31
- 35, The JSDoc `@example` blocks in AssignToProductionUseCase have corrupted
Markdown fences, where the opening or closing triple backticks are split by a
tab and break syntax highlighting. Fix the examples in
assign-to-production-use-case.ts by normalizing each fenced code block in the
affected JSDoc comments so the backticks are valid Markdown, and apply the same
correction consistently across all three examples referenced in the class
documentation.
| static subscribe(eventBus: EventBusPort, useCase: MarkAsPaidUseCase): void { | ||
| eventBus.on(GlobalEvents.PAYMENT_COMPLETED, async (data: unknown) => { | ||
| try { | ||
| await useCase.execute(data as MarkAsPaidDTO); | ||
| } catch (error) { | ||
| console.error('Error processing PaymentCompleted event:', error); | ||
| } | ||
| }); |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
No ocultes la excepción del listener.
emit() deja de fallar aunque execute(...) no se haya aplicado, así que PAYMENT_COMPLETED puede darse por procesado cuando en realidad no lo está. Re-lanza el error (o enrútalo a retry/DLQ) para no romper el contrato del bus.
🔧 Posible ajuste
eventBus.on(GlobalEvents.PAYMENT_COMPLETED, async (data: unknown) => {
try {
await useCase.execute(data as MarkAsPaidDTO);
} catch (error) {
console.error('Error processing PaymentCompleted event:', error);
+ throw error;
}
});📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| static subscribe(eventBus: EventBusPort, useCase: MarkAsPaidUseCase): void { | |
| eventBus.on(GlobalEvents.PAYMENT_COMPLETED, async (data: unknown) => { | |
| try { | |
| await useCase.execute(data as MarkAsPaidDTO); | |
| } catch (error) { | |
| console.error('Error processing PaymentCompleted event:', error); | |
| } | |
| }); | |
| static subscribe(eventBus: EventBusPort, useCase: MarkAsPaidUseCase): void { | |
| eventBus.on(GlobalEvents.PAYMENT_COMPLETED, async (data: unknown) => { | |
| try { | |
| await useCase.execute(data as MarkAsPaidDTO); | |
| } catch (error) { | |
| console.error('Error processing PaymentCompleted event:', error); | |
| throw error; | |
| } | |
| }); |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@modules/orders/application/mark-as-paid-use-case.ts` around lines 56 - 63,
The PAYMENT_COMPLETED listener in MarkAsPaidUseCase.subscribe is swallowing
exceptions by only logging them, which makes EventBusPort.emit think processing
succeeded. Update the async handler to rethrow the error after catching it, or
route it through the bus’s retry/DLQ path if available, so failures from
useCase.execute(data as MarkAsPaidDTO) are not hidden and the event contract
remains intact.
| try { | ||
| await main(); | ||
| } catch (error) { | ||
| console.error('? Seed failed:', error); |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Carácter corrupto en el mensaje de error.
'? Seed failed:' parece un emoji corrupto (mojibake), ya que el resto del script usa emojis reales (🌱, ✓, ✅) en sus logs. Verificar la codificación del archivo y restaurar el emoji original (p.ej. ❌).
✏️ Fix sugerido
- console.error('? Seed failed:', error);
+ console.error('❌ Seed failed:', error);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| console.error('? Seed failed:', error); | |
| console.error('❌ Seed failed:', error); |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@prisma/seed.ts` at line 608, The error log in the seed failure handler uses a
corrupted character in the message, so update the console.error call in seed.ts
to restore the intended emoji/text instead of the mojibake string. Check the
surrounding seed logging style used in the same script and replace the broken
prefix in the catch block with the correct failure marker so it matches the
other emoji-based logs.
Chore/better lint rules v3
Summary by CodeRabbit